//To drive you system, create the following Client class and include this exact class with your submission:
package cscie160.project.client;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.UnknownHostException;
import cscie160.project.atm.ATM;
import cscie160.project.atm.ATMFactory;
import cscie160.project.atm.ATMException;
import cscie160.project.atm.TransactionNotification;
import cscie160.project.atm.ATMListener;
import cscie160.project.bank.AccountInfo;
import java.util.HashMap;
  
public class Client 
    extends java.rmi.server.UnicastRemoteObject implements ATMListener
{
    /**
    * keeps the pool of accounts used in the test launch
    */
    private HashMap<Integer, AccountInfo> testAccountInfo;
    
    /**
    * ATM instance obtained by client
    */
    private ATM atm;
    
    public static void main(String[] args)
    {
        Client client;
        try{
            client = new Client();
        } catch (RemoteException ex) {
            ex.printStackTrace();
            return;
        }
        ATM atm = null;
        int atmPort = 1099;
        int bankServerPort = 1099;
        if (args.length > 0 && !(args[0].equals("?") || args[0].equalsIgnoreCase("help")))
        {
            int argNum = 0;
            try
            {
                atmPort = Integer.parseInt(args[0]);
                if (args.length > 1)
                {
                    argNum = 1;
                    bankServerPort = Integer.parseInt(args[1]);
                }
            } catch (NumberFormatException nfex)
            {
                System.out.println("Provided argument number " + Integer.toString(argNum) +
                    " \"" + args[argNum] + "\" can't be a port");
                return;
            }
        }


        atm = client.obtainAtm(atmPort, bankServerPort);
        if (atm == null)
            return;
        client.setAtm(atm);
        client.testATM();
    }

    public Client() throws java.rmi.RemoteException
    {
        super();
    }
    /**
    * obtains atm for the client
    * @param atmPort int
    * @return ATM
    */
    public ATM obtainAtm(int atmPort, int bankServerPort)
    {
        ATM atm = null;
        //int bankServerPort = 50001;
        try
        {
           String atmFactoryLookupUrl = "//localhost:" + Integer.toString(atmPort) + "/atmfactory";
           ATMFactory factory = (ATMFactory)Naming.lookup(atmFactoryLookupUrl);
           System.out.println("Client.main(): factory=" +
               (factory==null ? "null" : factory.hashCode()) + " is gotten");
           atm = factory.getATM(bankServerPort);

           System.out.println("Client.main(): atm=" +
               (atm==null ? "null" : atm.hashCode()) +" is gotten");

           registerItself(atm);
        } catch (MalformedURLException mue)
        {
             mue.printStackTrace();
        } catch (NotBoundException nbe)
        {
            nbe.printStackTrace();
        } catch (UnknownHostException uhe)
        {
            uhe.printStackTrace();
        } catch (RemoteException rex)
        {
            rex.printStackTrace();
        }
        return atm;
    }
    
    /**
    * sets ATM obtained by the client as client's ATM
    * @param atm ATM
    */
    protected void setAtm(ATM atm)
    {
        this.atm = atm;
    }
    
    /**
    * notificationHandler method
    * @param notification TransactionNotification 
    */
    public void receiveTransactionNotification (TransactionNotification notification)
    {
        System.out.println("\t" + "TransactionNotification: " + notification.toString());
    }
    
    /**
    * registers the client with an ATM as the ATMListener
    * @param atm ATM
    */
    public boolean registerItself(ATM atm)
    {
        try
        {
            boolean isRegisterd = atm.registerListener(this);
            return isRegisterd;
        } catch (java.rmi.RemoteException rex)
        {
            System.out.println(getClass().getName() + ": registerItself. exception=" + rex.getMessage()); 
            return false;
        }
    }

    /**
    * performs set of tests
    */
    public void testATM()
    {
        if (atm == null)
            return;
        System.out.println("Initial Balances:" + System.getProperty("line.separator"));
        printBalances();

        performTestOne();
        performTestTwo();
        performTestThree();
        performTestFour();
        performTestFive();
        performTestSix();
        performTestSeven();
        performTestEight();
        performTestNine();

        System.out.println(System.getProperty("line.separator") + "Final Balances:");
        printBalances();
    }

    /**
    * performs test 1
    */
    private void performTestOne()
    {
        System.out.println(callerName() + ": ");
        try {
            atm.getBalance(getAccountInfo(0000001, 5555));
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tFailed as expected: "+e);
        }
    }

    /**
    * performs test 2
    */
    private void performTestTwo()
    {
        System.out.println(callerName() + ": ");
        try
        {
            atm.withdraw(getAccountInfo(0000002, 2345), 500);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tFailed as expected: "+e);
        }
    }

    /**
    * performs test 3
    */
    private void performTestThree()
    {
        System.out.println(callerName() + ": ");    
        try
        {
            atm.withdraw(getAccountInfo(0000001, 1234), 50);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tFailed as expected: "+e);
        }
    }

    /**
    * performs test 4
    */
    private void performTestFour()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.deposit(getAccountInfo(0000001, 1234), 500);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tUnexpected error: "+e);
        }
    }

    /**
    * performs test 5
    */
    private void performTestFive()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.deposit(getAccountInfo(0000002, 2345), 100);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tUnexpected error: "+e);
        }
    }

    /**
    * performs test 6
    */
    private void performTestSix()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.withdraw(getAccountInfo(0000001, 1234), 100);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tUnexpected error: "+e);
        }
    }

    /**
    * performs test 7
    */
    private void performTestSeven()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.withdraw(getAccountInfo(0000003, 3456), 300);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tUnexpected error: "+e);
        }
    }

    /**
    * performs test 8.
    */
    private void performTestEight()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.withdraw(getAccountInfo(0000001, 1234), 200);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tFailed as expected: "+e);
        }    
    }

    /**
    * performs test 9
    */
    private void performTestNine()
    {
        System.out.println(callerName() + ": ");     
        try
        {
            atm.transfer(getAccountInfo(0000001, 1234), getAccountInfo(0000002, 2345), 100);
            System.out.println("\tSucceeded");
        } catch (Exception e)
        {
            System.out.println("\tUnexpected error: "+e);
        }    
    }

    /**
    * returns the name of caller method
    */
    private String callerName()
    {
        StackTraceElement[] els = Thread.currentThread().getStackTrace();
        String name = null, prevName = null;
        for (int i =0; i < els.length; i++)
        {
            name = els[i].getMethodName();
            if (prevName != null && prevName.equals("callerName"))
                break;
            prevName = name;
        }
        return name;
    }
    
    /**
    * prints all the test balances
    */
    public void printBalances()
    {        
        try
        {
            AccountInfo aInfo1 = getAccountInfo(0000001, 1234);
            AccountInfo aInfo2 = getAccountInfo(0000002, 2345);
            AccountInfo aInfo3 = getAccountInfo(0000003, 3456);
            System.out.println("Balance(" + aInfo1.getAccountNumber()+ "): " + atm.getBalance(aInfo1));
            System.out.println("Balance(" + aInfo2.getAccountNumber()+ "): " + atm.getBalance(aInfo2));
            System.out.println("Balance(" + aInfo3.getAccountNumber()+ "): " + atm.getBalance(aInfo3));
        } catch (RemoteException rex)
        {
            System.out.println("An exception occurred while communicating with the ATM");
            rex.printStackTrace();
        } catch (ATMException atmex)
        {
            System.out.println(atmex.getMessage());
        }
    }
    
    /**
    * creates and stores or finds AccountInfo by account number and PIN
    * @param accountId int
    * @param pin int
    * @return AccountInfo
    */
    private AccountInfo getAccountInfo(int accountId, int pin)
    {
        AccountInfo aInfo;
        if (testAccountInfo == null)
            testAccountInfo = new HashMap<Integer, AccountInfo>();
        boolean isIdPresent = testAccountInfo.containsKey(accountId);
            
        if (isIdPresent)
        {
            aInfo = testAccountInfo.get(accountId);
            if (aInfo.getPin() == pin)
                return aInfo;
        }
        aInfo = new AccountInfo(accountId, pin);
        testAccountInfo.put(accountId, aInfo);
        return aInfo;
    }
}